/*
 * EvoAlghorithm.cpp
 *
 *  Created on: 15.11.2012
 *      Author: shadius
 */

#include "EvoAlghorithm.h"
#include <boost/random.hpp>
#include "FitnessComputer.h"
#include <ctime>
#include <time.h>

#include "tbb/task_scheduler_init.h"
#include "boost/thread/mutex.hpp"
#include "PoolManager.h"
EvoAlghorithm::EvoAlghorithm(const shared_ptr<IFastDataApprox> &BGValues,
		const shared_ptr<IFastDataApprox> &IGValues,
		const shared_ptr<vector<double>> p_ECGValues, INPUT_PARAMS params) :
		p_BGValues(BGValues), p_IGValues(IGValues), p_ECGValues(p_ECGValues), params(
				params), checker(Checker(params)) {
	srand(time(NULL) + getpid());

}

void EvoAlghorithm::mutate(double& mutationRate,
		int& iInd, LINDIVIDUUM population) {
	//mutace
	for (int child = 0; child < 2; child++) {
		for (int i = 0; i < PARAMETER_COUNT; i++) {
			if ((i == PI && params.isPset) || (i == CI && params.isCset))
				continue;

			if (random() < mutationRate) {
				double randomValue = 0.2 * sqrt(-2 * log(random(0, 1)))
						* sin(2 * M_PI * random(0, 1));
				randomValue +=  getIndividuumValue(&population[iInd + child], i); // (*p_newPopulation)(iInd + child, i);
				if (checker.checkRange(i, randomValue)) {
					//(*p_newPopulation)(iInd + child, i) = randomValue;
					updateIndividuum(&population[iInd + child], i, randomValue);
				}
			}
		}
	}
}

double EvoAlghorithm::getIndividuumValue(INDIVIDUUM *individuum, int i) {

	switch (i) {
	case KI:
		return individuum->k;
		break;
	case PI:
		return individuum->p;
		break;
	case CGI:
		return individuum->cg ;
		break;
	case CI:
		return individuum->c;
		break;
	case DTI:
		return individuum->dt;
		break;
	default:
		std::cout << "Out of individuum struct" << endl;
		return -1;
		break;
	}
}
void EvoAlghorithm::updateIndividuum(INDIVIDUUM *individuum, int i, double value) {

	switch (i) {
	case KI:
		individuum->k = value;
		break;
	case PI:
		individuum->p = value;
		break;
	case CGI:
		individuum->cg = value;
		break;
	case CI:
		individuum->c = value;
		break;
	case DTI:
		individuum->dt = value;
		break;
	default:
		std::cout << "Out of individuum struct" << endl;
		break;
	}
}


void EvoAlghorithm::initializePopulation() {
	//inicializujeme populaci
	for (int i = 0; i < POPULATION_SIZE; i++) {

		population[i].p = (
				params.isPset ?
						params.p :
						random(PARAM_P_DEFAULT_MIN, PARAM_P_DEFAULT_MAX));
		population[i].cg = random(PARAM_CG_DEFAULT_MIN,
				PARAM_CG_DEFAULT_MAX);
		//cout << "CG: " << (*p_population)(i, CGI) << endl;
		population[i].c = (
				params.isCset ?
						params.c :
						random(PARAM_C_DEFAULT_MIN, PARAM_C_DEFAULT_MAX));

		double k = 0;
		double dt = 0;
		findKandDT(&k, &dt);
		population[i].dt = dt;
		population[i].k = k;
	}
}

INDIVIDUUM EvoAlghorithm::compute(LPAPPROXPOINT bgAPPROXPOINTS,
		LPAPPROXPOINT igAPPROXPOINTS, int len) {


	tbb::task_scheduler_init init(params.threadCount);
	double mutationRate = MIN_MUTATION_RATE;
	cout << "vypočteme fitness vsech jedincu v populaci" << cout;

	//inicializujeme populaci
	initializePopulation();
	boost::mutex mutex;
	double previousFitness = 0.0f;
	PoolManager poolBg(bgAPPROXPOINTS, params.threadCount, len);
	PoolManager poolIG(igAPPROXPOINTS, params.threadCount, len);

	for (int iGeneration = 0; iGeneration < GENERATIONS_NUM; iGeneration++) {
		double bestFitness = 0.0f;
		int bestFitnessIdx = 0;
		double minFitness = numeric_limits<double>::infinity();


		INDIVIDUUM newPopulation[POPULATION_SIZE];
		THREAD_RESULTS result = {0 , 0,numeric_limits<double>::infinity()};
		struct timespec start, finish;
		double elapsed;

		clock_gettime(CLOCK_MONOTONIC, &start);

		tbb::parallel_for(tbb::blocked_range<int>(0, POPULATION_SIZE, 50),
				FitnessComputer(&result,
						//bgAPPROXPOINTS,
						//igAPPROXPOINTS,
						poolBg,
						poolIG,
						len,
						this, checker, &mutex));


	//	FitnessComputer  computer(&result,
	//			poolBg,
	//			poolIG,
	//			len,this, checker, NULL);
//
	//	computer.serial(0, POPULATION_SIZE, bgAPPROXPOINTS, igAPPROXPOINTS);

		clock_gettime(CLOCK_MONOTONIC, &finish);
		elapsed = (finish.tv_sec - start.tv_sec);
		elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;
		cout << "Time Difference: " << elapsed << endl;

		//computer.paralelLambda(0, POPULATION_SIZE);
		bestFitness = result.bestFitness;
		bestFitnessIdx = result.bestFitnessIdx;
		minFitness = result.minFitness;

//
//		for (unsigned i = 0; i < POPULATION_SIZE; i++) {
//			  clock_t StartLocal = clock();
//
//			  m_fitness[i] = fitness(i, bgAPPROXPOINTS, igAPPROXPOINTS, len);
//			//	cout << "nalezena fitness " << (*p_fitness)[i] << endl;
//
//			  // cout << "Time Difference Local: " << clock() - StartLocal << endl;
//
//
//			if (m_fitness[i] > bestFitness) {
//				bestFitness = m_fitness[i];
//				bestFitnessIdx = i;
//			} else
//			if (m_fitness[i] < minFitness) {
//				minFitness = m_fitness[i];
//			}
//
//		}


// upravime hodnoty fitness pro lepsi selekci od 0 do bestFitness-minFitness
		double sumFitness = 0.0;
		for (int i = 0; i < POPULATION_SIZE; i++) {
			m_fitness[i] -= minFitness;
			sumFitness += m_fitness[i];
		}

		// vytvoreni nove populace, ktera nahradi stavajici
		for (int iInd = 0; iInd < POPULATION_SIZE; iInd += 2) {
			int individuumIdx1 = pickRandomIndividuum(sumFitness);
			int individuumIdx2 = pickRandomIndividuum(sumFitness);
			if (individuumIdx1 < 0 || individuumIdx2 < 0) {
				cout << "to se mi nezdá" << endl;
			}
			//prirozeny vyber
			if (random() < CROSS_RATE) {
				for (int i = 0; i < PARAMETER_COUNT; i++) {
					if (random() < 0.5) {
						updateIndividuum(&newPopulation[iInd], i, getIndividuumValue(&population[individuumIdx1], i));
						//(*p_newPopulation)(iInd, i) = (*p_population)(individuumIdx1, i);
						updateIndividuum(&newPopulation[iInd +1], i, getIndividuumValue(&population[individuumIdx2], i));

						//(*p_newPopulation)(iInd + 1, i) = (*p_population)(individuumIdx2, i);

					} else {
						updateIndividuum(&newPopulation[iInd], i, getIndividuumValue(&population[individuumIdx2], i));
						updateIndividuum(&newPopulation[iInd + 1], i, getIndividuumValue(&population[individuumIdx1], i));

//						(*p_newPopulation)(iInd, i) = (*p_population)(
//								individuumIdx2, i);
//						(*p_newPopulation)(iInd + 1, i) = (*p_population)(
//								individuumIdx1, i);
					}
				}
				//mutace
				mutate(mutationRate, iInd, newPopulation);

			} else { //neni krizeni
				for (int i = 0; i < PARAMETER_COUNT; i++) {
					updateIndividuum(&newPopulation[iInd], i, getIndividuumValue(&population[individuumIdx1], i));
					updateIndividuum(&newPopulation[iInd + 1], i, getIndividuumValue(&population[individuumIdx2], i));

//					(*p_newPopulation)(iInd, i) = (*p_population)(
//							individuumIdx1, i);
//					(*p_newPopulation)(iInd + 1, i) = (*p_population)(
//							individuumIdx2, i);

				}
			}
		}

		//elitismus - zachování nejlepšího
		for (int i = 0; i < PARAMETER_COUNT; i++) {
			newPopulation[0] = population[bestFitnessIdx];
			//(*p_newPopulation)(0, i) = (*p_population)(bestFitnessIdx, i);
		}
		//p_population.reset();
		cout << "Generation " << iGeneration << ", best fitness "
				<< bestFitness << endl;
		memcpy(population, newPopulation, sizeof(newPopulation));
		if (bestFitness > previousFitness) {
	//		cout << "Generation " << iGeneration << ", best fitness "
	//				<< bestFitness << endl;
			previousFitness = bestFitness;
			mutationRate = MIN_MUTATION_RATE;
			//V první generaci začít s nejnižší mutací a v každé následující generaci mutaci zvýšit.
			//Mutace se znova vrátí na nejnižší hodnotu v případě, že v nové generaci je nalezen lepší jedinec
			//než v předchozí anebo v případě, že mutace dosáhla své maximální hodnoty.
			//
			//
		} else {
			if (mutationRate == MAX_MUTATION_RATE)
				mutationRate = MIN_MUTATION_RATE;
			else
				mutationRate += MUTATION_STEP;

		}
	}
	cout << "p = " << population[0].p << endl;
	cout << "cg = " << population[0].cg << endl;
	cout << "c = " << population[0].c << endl;
	cout << "k = " << population[0].k << endl;
	cout << "dt = " << population[0].dt << endl;
	double sum = 0;
	int count = 0;
	double p = population[0].p;
	double cg = population[0].cg ;
	double c =  population[0].c;
	double dt = population[0].dt;
	double k = population[0].k ;
	if (countAverage(bgAPPROXPOINTS, igAPPROXPOINTS, p, cg, c, dt, k, len, &sum,
			&count) == E_INVALIDARG) {
		cout << "Pruser!!" << endl;
		for (unsigned i = 0; i < p_ECGValues->size(); i++) {
			if (!checker.checkTimeJumpRange(k, dt, (*p_ECGValues)[i])) {
				cout << "FAIIIIL JUMPY!!" << endl;
			}
		}

		if (!checker.checkRangeDT(dt)) {
			cout << "FAIIIIL DT!!" << endl;
		}
	} else {
		cout << "soucet rozdilu je: " << sum;
		cout << "pocet prvku je: " << count;
		cout << "average je: " << sum / (double) (count);

	}

	INDIVIDUUM res;
	res.c = c;
	res.cg = cg;
	res.dt = dt;
	res.k = k;
	res.p = p;

	return res;
}
int EvoAlghorithm::pickRandomIndividuum(double sumFitness) {
	double sum = 0.0;
	double fitnessPoint = random() * sumFitness;
	for (int i = 0; i < POPULATION_SIZE; i++) {
		sum += m_fitness[i];
		if (sum > fitnessPoint)
			return i;
	}
	return 0;
}

double EvoAlghorithm::fitness(int iInd, LPAPPROXPOINT bgAPPROXPOINTS,
		LPAPPROXPOINT igAPPROXPOINTS, int len) {
	double p = population[iInd].p;//(iInd, PI);
	double cg = population[iInd].cg;
	double c = population[iInd].c;
	double dt = population[iInd].dt;
	double k = population[iInd].k;

	int count = 0;
	double sum = 0;
	if (countAverage(bgAPPROXPOINTS, igAPPROXPOINTS, p, cg, c, dt, k, len, &sum,
	//TODO:něco udělat s tím E_INVALIDARG
			&count) == E_INVALIDARG) {
		return 0;
	}
	return 1.0 / (sum / (double) count);

}
void EvoAlghorithm::writeFitness(double fitness, int i) {
	m_fitness[i] = fitness;
}


HRESULT EvoAlghorithm::countAverage(LPAPPROXPOINT bgAPPROXPOINTS,
		LPAPPROXPOINT igAPPROXPOINTS, double p, double cg, double c, double dt,
		double k, int len, double *sum, int *count) {

	double localSum = 0;
	int localCount = 0;
	for (int i = 0; i < len; i++) {

		int igPoint = 0;
		double ecg = (*p_ECGValues)[i];
		if (!checker.checkRangeC(c) || !checker.checkRangeCG(cg) || !checker.checkRangeP(p)
				|| !checker.checkRangeDT(dt) || !checker.checkTimeJumpRange(k, dt, ecg)) {
			return E_INVALIDARG;
		}
		p_BGValues->GetPrecedingPoint(
				TimeUtils::convertSecToWin(i) + dt + k * dt * ecg, &igPoint);

		if (igPoint < len && igPoint > 0) {
			double dif = fabs(
					p * bgAPPROXPOINTS[i].y + cg * bgAPPROXPOINTS[i].y * ecg + c
							- igAPPROXPOINTS[igPoint].y);

			localCount++;
			localSum += dif;
		}

	}
	*count = localCount;
	*sum = localSum;
	return S_OK;
}

double EvoAlghorithm::random() {
	return ((double) rand() / (double) RAND_MAX);
}

double EvoAlghorithm::random(double min, double max) {
	double r = random();
	return min + r * (max - min);
}

void EvoAlghorithm::findKandDT(double *k, double *dt) {
	vector<double>::const_iterator it;

	double found = true;
	double localK;
	double localDt;
	while (found) {
		if (params.isKset) {
			localK = random(params.kMin, params.kMax);
		} else {
			localK = random();
		}
		localDt = random(params.dtMin, params.dtMax);

		for (unsigned i = 0; i < p_ECGValues->size(); i++) {
			found = !checker.checkTimeJumpRange(localK, localDt, (*p_ECGValues)[i]);
			if (found)
				break;
		}
	}
	*k = localK;
	*dt = localDt;

}

EvoAlghorithm::~EvoAlghorithm() {
}
//TODO:terminovani scheduleru?
